layout.tsx 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. 'use client'
  2. import type { FC } from 'react'
  3. import { useUnmount } from 'ahooks'
  4. import React, { useCallback, useEffect, useState } from 'react'
  5. import { usePathname, useRouter } from 'next/navigation'
  6. import {
  7. RiDashboard2Fill,
  8. RiDashboard2Line,
  9. RiFileList3Fill,
  10. RiFileList3Line,
  11. RiTerminalBoxFill,
  12. RiTerminalBoxLine,
  13. RiTerminalWindowFill,
  14. RiTerminalWindowLine,
  15. } from '@remixicon/react'
  16. import { useTranslation } from 'react-i18next'
  17. import { useShallow } from 'zustand/react/shallow'
  18. import { useContextSelector } from 'use-context-selector'
  19. import s from './style.module.css'
  20. import cn from '@/utils/classnames'
  21. import { useStore } from '@/app/components/app/store'
  22. import AppSideBar from '@/app/components/app-sidebar'
  23. import type { NavIcon } from '@/app/components/app-sidebar/navLink'
  24. import { fetchAppDetail, fetchAppSSO } from '@/service/apps'
  25. import AppContext, { useAppContext } from '@/context/app-context'
  26. import Loading from '@/app/components/base/loading'
  27. import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
  28. export type IAppDetailLayoutProps = {
  29. children: React.ReactNode
  30. params: { appId: string }
  31. }
  32. const AppDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
  33. const {
  34. children,
  35. params: { appId }, // get appId in path
  36. } = props
  37. const { t } = useTranslation()
  38. const router = useRouter()
  39. const pathname = usePathname()
  40. const media = useBreakpoints()
  41. const isMobile = media === MediaType.mobile
  42. const { isCurrentWorkspaceEditor } = useAppContext()
  43. const { appDetail, setAppDetail, setAppSiderbarExpand } = useStore(useShallow(state => ({
  44. appDetail: state.appDetail,
  45. setAppDetail: state.setAppDetail,
  46. setAppSiderbarExpand: state.setAppSiderbarExpand,
  47. })))
  48. const [navigation, setNavigation] = useState<Array<{
  49. name: string
  50. href: string
  51. icon: NavIcon
  52. selectedIcon: NavIcon
  53. }>>([])
  54. const systemFeatures = useContextSelector(AppContext, state => state.systemFeatures)
  55. const getNavigations = useCallback((appId: string, isCurrentWorkspaceEditor: boolean, mode: string) => {
  56. const navs = [
  57. ...(isCurrentWorkspaceEditor
  58. ? [{
  59. name: t('common.appMenus.promptEng'),
  60. href: `/app/${appId}/${(mode === 'workflow' || mode === 'advanced-chat') ? 'workflow' : 'configuration'}`,
  61. icon: RiTerminalWindowLine,
  62. selectedIcon: RiTerminalWindowFill,
  63. }]
  64. : []
  65. ),
  66. {
  67. name: t('common.appMenus.apiAccess'),
  68. href: `/app/${appId}/develop`,
  69. icon: RiTerminalBoxLine,
  70. selectedIcon: RiTerminalBoxFill,
  71. },
  72. ...(isCurrentWorkspaceEditor
  73. ? [{
  74. name: mode !== 'workflow'
  75. ? t('common.appMenus.logAndAnn')
  76. : t('common.appMenus.logs'),
  77. href: `/app/${appId}/logs`,
  78. icon: RiFileList3Line,
  79. selectedIcon: RiFileList3Fill,
  80. }]
  81. : []
  82. ),
  83. {
  84. name: t('common.appMenus.overview'),
  85. href: `/app/${appId}/overview`,
  86. icon: RiDashboard2Line,
  87. selectedIcon: RiDashboard2Fill,
  88. },
  89. ]
  90. return navs
  91. }, [t])
  92. useEffect(() => {
  93. if (appDetail) {
  94. document.title = `${(appDetail.name || 'App')} - Dify`
  95. const localeMode = localStorage.getItem('app-detail-collapse-or-expand') || 'expand'
  96. const mode = isMobile ? 'collapse' : 'expand'
  97. setAppSiderbarExpand(isMobile ? mode : localeMode)
  98. // TODO: consider screen size and mode
  99. // if ((appDetail.mode === 'advanced-chat' || appDetail.mode === 'workflow') && (pathname).endsWith('workflow'))
  100. // setAppSiderbarExpand('collapse')
  101. }
  102. }, [appDetail, isMobile])
  103. useEffect(() => {
  104. setAppDetail()
  105. fetchAppDetail({ url: '/apps', id: appId }).then((res) => {
  106. // redirection
  107. const canIEditApp = isCurrentWorkspaceEditor
  108. if (!canIEditApp && (pathname.endsWith('configuration') || pathname.endsWith('workflow') || pathname.endsWith('logs'))) {
  109. router.replace(`/app/${appId}/overview`)
  110. return
  111. }
  112. if ((res.mode === 'workflow' || res.mode === 'advanced-chat') && (pathname).endsWith('configuration')) {
  113. router.replace(`/app/${appId}/workflow`)
  114. }
  115. else if ((res.mode !== 'workflow' && res.mode !== 'advanced-chat') && (pathname).endsWith('workflow')) {
  116. router.replace(`/app/${appId}/configuration`)
  117. }
  118. else {
  119. setAppDetail({ ...res, enable_sso: false })
  120. setNavigation(getNavigations(appId, isCurrentWorkspaceEditor, res.mode))
  121. if (systemFeatures.enable_web_sso_switch_component && canIEditApp) {
  122. fetchAppSSO({ appId }).then((ssoRes) => {
  123. setAppDetail({ ...res, enable_sso: ssoRes.enabled })
  124. })
  125. }
  126. }
  127. }).catch((e: any) => {
  128. if (e.status === 404)
  129. router.replace('/apps')
  130. })
  131. }, [appId, isCurrentWorkspaceEditor, systemFeatures, getNavigations, pathname, router, setAppDetail])
  132. useUnmount(() => {
  133. setAppDetail()
  134. })
  135. if (!appDetail) {
  136. return (
  137. <div className='flex h-full items-center justify-center bg-background-body'>
  138. <Loading />
  139. </div>
  140. )
  141. }
  142. return (
  143. <div className={cn(s.app, 'flex', 'overflow-hidden')}>
  144. {appDetail && (
  145. <AppSideBar title={appDetail.name} icon={appDetail.icon} icon_background={appDetail.icon_background} desc={appDetail.mode} navigation={navigation} />
  146. )}
  147. <div className="bg-components-panel-bg grow overflow-hidden">
  148. {children}
  149. </div>
  150. </div>
  151. )
  152. }
  153. export default React.memo(AppDetailLayout)